﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Diagnostics.Contracts;

namespace System.IO
{
    /// <summary>
    /// Allows to manage free drive letters in a smarter way.
    /// </summary>
    public class DriveHelper
    {
        readonly object _lock = new object();

        Queue<string> _driveLetters;

        #region Public API
        /// <summary>
        /// Returns the next available drive letter, starting at the end of the alphabet.
        /// </summary>
        /// <returns>drive letter</returns>
        public char GetNextFreeDrive()
        {
            ensureFreeDrivesQueueCreated();

            lock (_lock)
            {
                if (_driveLetters.Count == 0)
                    throw new ApplicationException("No free drive letter available!");

                return _driveLetters.Dequeue()[0];
            }
        }

        /// <summary>
        /// Allows reclaiming of previously used letters without complete resynch.
        /// </summary>
        /// <param name="driveLetter">drive letter</param>
        public void Reclaim(char driveLetter)
        {
            ContractEx.Check<InvalidOperationException>(_driveLetters != null, 
                "Can only reclaim previously claimed drive letters!");
            var dlToReclaim = driveLetter.ToString();
            ContractEx.Check<InvalidOperationException>(!_driveLetters.Contains(dlToReclaim),
                "Can only reclaim previously claimed drive letters!");

            lock (_lock)
            {
                _driveLetters.Enqueue(dlToReclaim);
            }
        }

        /// <summary>
        /// Resynchs to the currently available drives.
        /// </summary>
        public void Resynch()
        {
            lock (_lock)
            {
                _driveLetters = getDriveLettersReversed();
            }
        }

        /// <summary>
        /// Returns a copy of the current list of free drives.
        /// </summary>
        public string[] FreeDrives
        {
            get
            {
                ensureFreeDrivesQueueCreated();
                lock (_lock)
                {
                    return _driveLetters.ToArray(); 
                }
            }
        }
        #endregion

        private void ensureFreeDrivesQueueCreated()
        {
            if (_driveLetters == null)
            {
                lock (_lock)
                {
                    if (_driveLetters == null)
                    {
                        _driveLetters = getDriveLettersReversed();
                    }
                }
            }
        }

        private Queue<string> getDriveLettersReversed()
        {
            var q = from d in DriveInfo.GetDrives()
                    select d.Name.Substring(0, 1).ToLower();
            var o = q.OrderByDescending(p => p);
            return new Queue<string>(createDriveLetters().Except(o));
        }

        private static IEnumerable<string> createDriveLetters()
        {
            var letters = new List<string>();
            for (int i = Convert.ToInt32('z'); i >= Convert.ToInt32('a'); i--)
            {
                letters.Add(Convert.ToChar(i).ToString());
            }
            return letters.ToArray();
        }
    }
}
